package com.dliyun;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.dliyun.algorithm.AES;
import com.dliyun.algorithm.Rsa;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class DefaultClient {
    private static final Logger log = LoggerFactory.getLogger(DefaultClient.class);
    private final OkHttpClient httpClient;

    private final String serverUrl;
    private final String appKey;
    private final String encryptKey;
    private final AlgorithmType algorithmType;

    private String charset;
    private int connectTimeout;
    private int readTimeout;


    public DefaultClient(String serverUrl, String appKey, String encryptKey, AlgorithmType algorithmType, String charset, int connectTimeout, int readTimeout) {
        this.serverUrl = serverUrl;
        this.appKey = appKey;
        this.encryptKey = encryptKey;
        this.charset = charset;
        this.algorithmType = algorithmType;
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
        this.httpClient = new OkHttpClient();
    }

    public DefaultClient(String serverUrl, String appKey, String encryptKey, AlgorithmType algorithmType, String charset, int connectTimeout) {
        this(serverUrl, appKey, encryptKey, algorithmType, charset, connectTimeout, 15000);
    }

    public DefaultClient(String serverUrl, String appKey, String encryptKey, AlgorithmType algorithmType, String charset) {
        this(serverUrl, appKey, encryptKey, algorithmType, charset, 3000);
    }

    public DefaultClient(String serverUrl, String appKey, String encryptKey, AlgorithmType algorithmType) {
        this(serverUrl, appKey, encryptKey, algorithmType, "UTF-8");
    }

    public DefaultClient(String serverUrl, String appKey, String encryptKey) {
        this(serverUrl, appKey, encryptKey, AlgorithmType.AES);
    }

    public <T> Response<T> execute(String method, String version) throws ClientException {
        return this.execute(method, version, null);
    }

    public <T> Response<T> execute(String method, String version, Map<String, Object> params) throws ClientException {
        try {
            if (params == null) {
                params = new HashMap<>();
            }
            params.put("method", method);
            params.put("version", version);
            log.info("request params>>>>>>>>>>>>> {}", JSON.toJSONString(params));

            String requestBodyString = JSON.toJSONString(params);
            if (AlgorithmType.AES.equals(algorithmType)) {
                requestBodyString = AES.encryptToBase64(requestBodyString, this.encryptKey);
            }

            if (AlgorithmType.RSA.equals(algorithmType)) {
                requestBodyString = Rsa.encryptByPublicKey(requestBodyString, this.encryptKey);
            }

            RequestBody requestBody = RequestBody.create(requestBodyString, MediaType.parse("application/json"));

            Request.Builder request = new Request.Builder();
            request.url(this.serverUrl);
            request.header("x-app-key", this.appKey);
            request.post(requestBody);
            okhttp3.Response response = this.httpClient.newCall(request.build()).execute();

            if (response.isSuccessful()) {
                try {
                    String responseData = new String(Objects.requireNonNull(response.body()).bytes(), charset);
                    JSONObject jsonObject = JSON.parseObject(responseData);
                    if (jsonObject.containsKey("body")) {
                        String responseBody = jsonObject.getString("body");

                        if (AlgorithmType.AES.equals(algorithmType)) {
                            responseBody = AES.decryptFromBase64(responseBody, this.encryptKey);
                        }

                        if (AlgorithmType.RSA.equals(algorithmType)) {
                            responseBody = Rsa.decryptByPublicKey(responseBody, this.encryptKey);
                        }

                        T bodyObject = JSON.parseObject(responseBody, new TypeReference<T>() {
                        });
                        jsonObject.put("body", bodyObject);
                        responseData = jsonObject.toJSONString();
                    }
                    return JSON.parseObject(responseData, new TypeReference<Response<T>>() {
                    });
                } catch (Exception e) {
                    log.warn("decrypt error", e);
                    return null;
                }
            } else {
                log.warn("resp.code>>>>> {}", response.code());
                if (response.body() != null) {
                    log.warn("resp.body>>>>> {}", Objects.requireNonNull(response.body()).string());
                }
                throw new ClientException(String.format("http error code : %s", response.code()));
            }
        } catch (Exception e) {
            throw new ClientException(e);
        }
    }


    public String getServerUrl() {
        return serverUrl;
    }

    public String getAppKey() {
        return appKey;
    }

    public String getEncryptKey() {
        return encryptKey;
    }

    public AlgorithmType getAlgorithmType() {
        return algorithmType;
    }

    public String getCharset() {
        return charset;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }
}
